﻿using System;
using GraphicsFramework.Entity;
using GraphicsFramework.Property;
using System.Collections.Generic;

namespace GraphicsFramework
{
    public abstract class GraphicsFramework
    {

        #region Variable Declarations

        private int gridsize;
        private float gridmultiplier;
        private Point2F gridvalue;
        private Point2F center;
        private Point2F middle;
        private Entity.Point2F snapvalue;
        private int precision;

        protected int width;
        protected int height;
        protected float zoomvalue;
        protected int defaultgridsize;
        protected float movevalue;
        private float defaultmove;
        private bool realtimegrid;

        #endregion

        #region Constructors

        public GraphicsFramework(int width, int height, int gridsize, Point2F gridvalue)
        {
            this.gridvalue = new Point2F(1, 1);

            this.gridsize = gridsize;
            this.middle = new Point2F();
            this.center = new Point2F((float)(width * 0.5), (float)(height * 0.5));

            this.zoomvalue = 1;
            this.movevalue = 0.1F;
            this.defaultmove = this.movevalue;
            this.realtimegrid = false;

            this.defaultgridsize = gridsize;


            UpdateGridMultiplier();
            UpdateSnapAndPrecision();
        }

        #endregion

        #region Graphics Functions

        public abstract void SetGraphics(object graphics);
        public abstract Enum.EnumKeys GetKey(object e);
        public abstract Enum.EnumMouseButtons GetMouseButton(object e);

        public virtual void Resize(int width, int height)
        {
            center.X = (float)(width * 0.5);
            center.Y = (float)(height * 0.5);

            this.width = width;
            this.height = height;
        }

        public virtual void MoveLeft()
        {
            Move(new Point2F(), new Point2F(-movevalue * gridvalue.X, 0));
        }

        public virtual void MoveRight()
        {
            Move(new Point2F(), new Point2F(movevalue * gridvalue.X, 0));
        }

        public virtual void MoveUp()
        {
            Move(new Point2F(), new Point2F(0, movevalue * gridvalue.Y));
        }

        public virtual void MoveDown()
        {
            Move(new Point2F(), new Point2F(0, -movevalue * gridvalue.Y));
        }

        public virtual void Move(Point2F current, Point2F previous)
        {
            if (!current.IsEqual(previous, precision))
            {
                this.middle.X -= (current.X - previous.X) / this.gridvalue.X;
                this.middle.Y -= (current.Y - previous.Y) / this.gridvalue.Y;
            }
        }

        public virtual void Zoom(int x, int y, int value)
        {
            ///Zoom base on the given value
            Point2F prev = ToPoint(x, y);
            ZoomRealtime((float)(value * 0.05));

            ///Move base on the given position
            Point2F curr = ToPoint(x, y);
            Move(curr, prev);
        }

        public virtual void Zoom(float x1, float y1, float x2, float y2)
        {
            Point2F middle = new Point2F((x1 + x2) / 2, (y1 + y2) / 2);
            Point2F difference = new Point2F((float)Math.Abs((x1 - x2) / gridvalue.X), (float)Math.Abs((y1 - y2) / gridvalue.Y));

            if (((difference.X / difference.Y) >= (center.X / center.Y)))
            {
                if (difference.X == 0)
                    return;

                gridsize = (int)(2 * center.X / difference.X);
            }
            else
            {
                if (difference.Y == 0)
                    return;

                gridsize = (int)(2 * center.Y / difference.Y);
            }

            middle.X = middle.X / gridvalue.X;
            middle.Y = middle.Y / gridvalue.Y;

            UpdateZoom();
        }

        public virtual void ZoomIn()
        {
            gridsize = (int)(gridsize * 1.15);
            UpdateZoom();
        }

        public virtual void ZoomOut()
        {
            gridsize = (int)(gridsize / 1.15);
            UpdateZoom();
        }

        private void ZoomRealtime(float value)
        {
            int size = (int)(gridsize + (float)(value * gridsize) / (float)defaultgridsize);
            if (gridsize == size)
            {
                gridsize = (int)(size + value);
                UpdateZoom();
            }
            else
            {
                gridsize = size;
                UpdateZoom();
            }
        }

        private void UpdateZoom()
        {
            if (gridsize < 5)
                gridsize = 5;

            zoomvalue = gridsize / (float)defaultgridsize;
            movevalue = defaultmove / zoomvalue;

            UpdateGridMultiplier();
            UpdateSnapAndPrecision();
        }

        public int GetLineWidth(DrawProperty property)
        {
            if (property.ScaleOutline)
            {
                int width = (int)(zoomvalue * property.LineWidth);
                if (width < 1) width = 1;
                return width;
            }
            else
            {
                return property.LineWidth;
            }
        }

        public float GetRelativeThickness(DrawProperty property)
        {
            return GetLineWidth(property) / (float)(Gridsize);
        }

        #endregion

        #region Drawing Functions

        #region Lines

        /// <summary>
        /// Draws a line directly into the screen wihout conversion.
        /// </summary>
        /// <param name="property">Drawing properties</param>
        /// <param name="point1">First point</param>
        /// <param name="point2">Second point</param>
        public abstract void BasicLine(DrawProperty property, Point2I point1, Point2I point2);

        /// <summary>
        /// Draws a line in the current coordinate space.
        /// Given points will be converted into the screen coordinates.
        /// </summary>
        /// <param name="property">Drawing properties</param>
        /// <param name="point1">First point</param>
        /// <param name="point2">Second point</param>
        public abstract void DrawLine(DrawProperty property, Point2F point1, Point2F point2);

        #endregion

        #region Rectangles

        public abstract void BasicRectangle(DrawProperty property, Point2I point, int width, int height);

        public abstract void BasicRectangle(DrawProperty property, Point2F point, float width, float height);

        public abstract void DrawRectangle(DrawProperty property, Point2I point1, int width, int height);

        public abstract void DrawRectangle(DrawProperty property, Point2F point1, float width, float height);

        #endregion

        #region Circles

        public abstract void BasicCircle(DrawProperty property, Point2I point, float radius);

        public abstract void BasicCircle(DrawProperty property, Point2F point, float radius);

        /// <summary>
        /// Draws a circle in the current coordinate space.
        /// Given points will be converted into the screen coordinates.
        /// </summary>
        /// <param name="property">Drawing properties</param>
        /// <param name="point">Center of the circle</param>
        /// <param name="radius">Radius of the circle</param>
        public abstract void DrawCircle(DrawProperty property, Point2F point, float radius);

        #endregion

        #region Ellipse

        /// <summary>
        /// Draws a ellipse directly into the screen wihout conversion.
        /// </summary>
        /// <param name="property">Drawing properties</param>
        /// <param name="point1">First point</param>
        /// <param name="point2">Second point</param>
        public abstract void BasicEllipse(DrawProperty property, Point2I point1, Point2I point2);

        /// <summary>
        /// Draws a ellipse in the current coordinate space.
        /// Given points will be converted into the screen coordinates.
        /// </summary>
        /// <param name="property">Drawing properties</param>
        /// <param name="point1">First point</param>
        /// <param name="point2">Second point</param>
        public abstract void DrawEllipse(DrawProperty property, Point2F point1, Point2F point2);

        /// <summary>
        /// Draws a ellipse directly into the screen wihout conversion.
        /// </summary>
        /// <param name="property">Drawing properties</param>
        /// <param name="point">Center of the ellipse</param>
        /// <param name="width">Width of the ellipse</param>
        /// <param name="height">Height of the ellipse</param>
        public abstract void BasicEllipse(DrawProperty property, Point2I point, int width, int height);

        /// <summary>
        /// Draws a ellipse in the current coordinate space.
        /// Given points will be converted into the screen coordinates.
        /// </summary>
        /// <param name="property">Drawing properties</param>
        /// <param name="point">Center of the ellipse</param>
        /// <param name="width">Width of the ellipse</param>
        /// <param name="height">Height of the ellipse</param>
        public abstract void DrawEllipse(DrawProperty property, Point2F point1, float width, float height);

        #endregion

        #region Polylines

        public abstract void DrawPolyline(DrawProperty property, IList<Entity.Point2F> points);

        #endregion

        #region Polygons

        public abstract void DrawPolygon(DrawProperty property, IList<Entity.Point2F> points);

        #endregion

        #region Paths
        #endregion

        #region Text

        public abstract void DrawText(DrawProperty property, TextProperty textproperty, Entity.Point2F point, string text);
        public abstract Entity.Point2I MeasureString(TextProperty textproperty, string text);

        #endregion

        #endregion

        #region Properties

        public int Width
        {
            get { return width; }
            set { width = value; }
        }

        public int Height
        {
            get { return height; }
            set { height = value; }
        }

        public int DefaultGridsize
        {
            get { return defaultgridsize; }
            set { defaultgridsize = value; gridsize = value; }
        }

        public float Zoomvalue
        {
            get { return zoomvalue; }
            set { zoomvalue = value; }
        }

        public Point2F Middle
        {
            get { return middle; }
        }

        public Point2F Center
        {
            get { return center; }
            set { center = value; }
        }

        public int Gridsize
        {
            get { return gridsize; }
        }

        public Point2F GridValue
        {
            get { return this.gridvalue; }
            set
            {
                this.gridvalue = value;
                UpdateSnapAndPrecision();
            }
        }

        private void UpdateGridMultiplier()
        {
            float div = (float) Math.Log10(gridsize / 50F);

            if (div >= 0)
            {
                int mult = (int)Math.Floor(div);
                gridmultiplier = (float)Math.Pow(10, mult); ;
            }
            else
            {
                int mult = (int)Math.Floor(-div) + 1;
                gridmultiplier = 1 / (float)Math.Pow(10, mult);
            }
        }

        private void UpdateSnapAndPrecision()
        {
            this.snapvalue = new Point2F(this.gridvalue.X / (10 * gridmultiplier), this.gridvalue.Y / (10 * gridmultiplier));

            string[] x = this.snapvalue.X.ToString().Split('.');
            string[] y = this.snapvalue.Y.ToString().Split('.');

            if (x.Length == 2)
            {
                if (y.Length == 2)
                {
                    if (x[1].Length > y[1].Length)
                        this.precision = x[1].Length + 1;
                    else
                        this.precision = y[1].Length + 1;
                }
                else
                    this.precision = x[1].Length + 1;
            }
            else if (y.Length == 2)
                this.precision = y[1].Length + 1;
            else
                this.precision = 1;
        }

        public float GridMultiplier
        {
            get { return gridmultiplier; }
        }

        public Entity.Point2F SnapValue
        {
            get { return snapvalue; }
        }

        public int Precision
        {
            get { return precision; }
        }

        #endregion

        #region Conversion Functions

        public int ToCoordX(float pointX)
        {
            return (int)(center.X + (pointX / gridvalue.X - middle.X) * gridsize);
        }

        public int ToCoordY(float pointY)
        {
            return (int)(center.Y - (pointY / gridvalue.Y - middle.Y) * gridsize);
        }

        public Point2I ToCoord(Point2F point)
        {
            return new Point2I(ToCoordX(point.X), ToCoordY(point.Y));
        }

        public Point2I ToCoord(float pointX, float pointY)
        {
            return new Point2I(ToCoordX(pointX), ToCoordY(pointY));
        }

        public float ToPointX(int coordX)
        {
            return (middle.X - (center.X - coordX) / gridsize) * gridvalue.X;
        }

        public float ToPointY(int coordY)
        {
            return (middle.Y + (center.Y - coordY) / gridsize) * gridvalue.Y;
        }

        public Point2F ToPoint(Point2I coord)
        {
            return new Point2F(ToPointX(coord.X), ToPointY(coord.Y));
        }

        public Point2F ToPoint(int coordX, int coordY)
        {
            return new Point2F(ToPointX(coordX), ToPointY(coordY));
        }

        public int ToCoordWidth(float pointwidth)
        {
            return (int)(pointwidth * gridsize / gridvalue.X);
        }

        public int ToCoordHeight(float pointwidth)
        {
            return (int)(pointwidth * gridsize / gridvalue.Y);
        }

        public float ToPointWidth(float coordwidth)
        {
            return coordwidth * gridvalue.X / ((float)gridsize);
        }

        public float ToPointHeight(float coordwidth)
        {
            return coordwidth * gridvalue.Y / ((float)gridsize);
        }

        #endregion
    }
}